//==============================================================================
// Copyright (c) 2016-2020 Advanced Micro Devices, Inc. All rights reserved.
/// \author AMD Developer Tools Team
/// \file
/// \brief  Manages a set of derived counters
//==============================================================================

#include <stdio.h>
#include <sstream>
#include <string.h>  // for strcpy
#include <algorithm>

#include "utility.h"
#include "logging.h"
#include "gpa_derived_counter.h"
#include "gpa_common_defs.h"
#include "gpa_hardware_counters.h"

#ifdef __cplusplus
#include <cstdint>
#else
#include <stdint.h>
#endif

GPA_DerivedCounter::GPA_DerivedCounter()
    : m_index(0u)
    , m_pName(nullptr)
    , m_pGroup(nullptr)
    , m_pDescription(nullptr)
    , m_dataType(GPA_DATA_TYPE__LAST)
    , m_usageType(GPA_USAGE_TYPE__LAST)
    , m_pComputeExpression(nullptr)
    , derived_counter_hardware_info_(nullptr)
    , derived_counter_info_init_(false)
{
}

GPA_DerivedCounter::GPA_DerivedCounter(unsigned int        index,
                                       const char*         pName,
                                       const char*         pGroup,
                                       const char*         pDescription,
                                       GPA_Data_Type       dataType,
                                       GPA_Usage_Type      usageType,
                                       vector<gpa_uint32>& internalCountersRequired,
                                       const char*         pComputeExpression,
                                       const char*         pUuid)
    : m_index(index)
    , m_pName(pName)
    , m_pGroup(pGroup)
    , m_pDescription(pDescription)
    , m_dataType(dataType)
    , m_usageType(usageType)
    , m_internalCountersRequired(internalCountersRequired)
    , m_pComputeExpression(pComputeExpression)
    , derived_counter_hardware_info_(nullptr)
    , derived_counter_info_init_(false)
{
    uint32_t bytes[8];
#ifdef _WIN32
    sscanf_s(pUuid,
             "%08lX-%04hX-%04hX-%02X%02X-%02X%02X%02X%02X%02X%02X",
             &m_uuid.Data1,
             &m_uuid.Data2,
             &m_uuid.Data3,
             &bytes[0],
             &bytes[1],
             &bytes[2],
             &bytes[3],
             &bytes[4],
             &bytes[5],
             &bytes[6],
             &bytes[7]);

    for (int i = 0; i < _countof(bytes); ++i)
    {
        m_uuid.Data4[i] = static_cast<unsigned char>(bytes[i]);
    }

#else
    /*
    Autogenerated hash is MD5 hash which are fixed length of 128-bits (16 bytes).
    */
    uint32_t data1;
    static_assert(sizeof(short) == sizeof(uint16_t), "short is more than 2 bytes for UUID");
    sscanf(pUuid,
           "%08uiX-%04hX-%04hX-%02X%02X-%02X%02X%02X%02X%02X%02X",
           &data1,
           &m_uuid.m_data2,
           &m_uuid.m_data3,
           &bytes[0],
           &bytes[1],
           &bytes[2],
           &bytes[3],
           &bytes[4],
           &bytes[5],
           &bytes[6],
           &bytes[7]);

    /*
    m_data1 of GPA_UUID is a 'long' type which is 8 bytes on GCC/clang on Linux
    We need to copy 4 bytes of data1 to m_data1 until we update the GPA_UUID structure
    to match with standard MD5 struct. This change will be non-backward compatible
    change due to difference in ABI. It should be changed in GPA 4.0
    */
    memset(&m_uuid.m_data1, 0, sizeof(m_uuid.m_data1));
    memcpy(&m_uuid.m_data1, &data1, sizeof(uint32_t));

    for (size_t i = 0; i < (sizeof(bytes) / sizeof(bytes[0])); ++i)
    {
        m_uuid.m_data4[i] = static_cast<unsigned char>(bytes[i]);
    }

#endif
}

GPA_DerivedCounter::~GPA_DerivedCounter()
{
    delete derived_counter_hardware_info_;
}

bool GPA_DerivedCounter::InitializeDerivedCounterHardwareInfo(const IGPACounterAccessor* gpa_counter_accessor)
{
    bool counter_init = false;
    if (nullptr != derived_counter_hardware_info_)
    {
        const GPA_HardwareCounters* hardware_counters = gpa_counter_accessor->GetHardwareCounters();

        for (auto iter = m_internalCountersRequired.cbegin(); iter != m_internalCountersRequired.cend(); ++iter)
        {
            if (*iter == hardware_counters->m_gpuTimeBottomToBottomDurationCounterIndex)
            {
                derived_counter_hardware_info_->is_gpu_time                        = true;
                derived_counter_hardware_info_->gpu_time_bottom_to_bottom_duration = hardware_counters->m_gpuTimeBottomToBottomDurationCounterIndex;
                counter_init                                                       = true;
            }
            else if (*iter == hardware_counters->m_gpuTimeBottomToBottomStartCounterIndex)
            {
                derived_counter_hardware_info_->is_gpu_time                     = true;
                derived_counter_hardware_info_->gpu_time_bottom_to_bottom_start = hardware_counters->m_gpuTimeBottomToBottomStartCounterIndex;
                counter_init                                                    = true;
            }
            else if (*iter == hardware_counters->m_gpuTimeBottomToBottomEndCounterIndex)
            {
                derived_counter_hardware_info_->is_gpu_time                   = true;
                derived_counter_hardware_info_->gpu_time_bottom_to_bottom_end = hardware_counters->m_gpuTimeBottomToBottomEndCounterIndex;
                counter_init                                                  = true;
            }
            else if (*iter == hardware_counters->m_gpuTimeTopToBottomDurationCounterIndex)
            {
                derived_counter_hardware_info_->is_gpu_time                     = true;
                derived_counter_hardware_info_->gpu_time_top_to_bottom_duration = hardware_counters->m_gpuTimeTopToBottomDurationCounterIndex;
                counter_init                                                    = true;
            }
            else if (*iter == hardware_counters->m_gpuTimeTopToBottomStartCounterIndex)
            {
                derived_counter_hardware_info_->is_gpu_time                  = true;
                derived_counter_hardware_info_->gpu_time_top_to_bottom_start = hardware_counters->m_gpuTimeTopToBottomStartCounterIndex;
                counter_init                                                 = true;
            }
            else if (*iter == hardware_counters->m_gpuTimeTopToBottomEndCounterIndex)
            {
                derived_counter_hardware_info_->is_gpu_time                = true;
                derived_counter_hardware_info_->gpu_time_top_to_bottom_end = hardware_counters->m_gpuTimeTopToBottomEndCounterIndex;
                counter_init                                               = true;
            }
            else
            {
                GpaHwCounter hw_counter;
                derived_counter_hardware_info_->is_gpu_time = false;

                if (hardware_counters->GetHardwareInfo(*iter, hw_counter))
                {
                    hw_counter_info_list_.push_back(hw_counter);
                }
            }
        }

        if (!derived_counter_hardware_info_->is_gpu_time)
        {
            if (m_internalCountersRequired.size() == hw_counter_info_list_.size())
            {
                derived_counter_hardware_info_->gpa_hw_counter_count = static_cast<gpa_uint32>(m_internalCountersRequired.size());
                derived_counter_hardware_info_->counter_usage_type   = m_usageType;
                derived_counter_hardware_info_->gpa_hw_counters      = hw_counter_info_list_.data();
                counter_init                                         = true;
            }
        }
    }

    return counter_init;
}

GpaDerivedCounterInfo* GPA_DerivedCounter::GetDerivedCounterHardwareInfo(const IGPACounterAccessor* gpa_counter_accessor)
{
    if (nullptr == derived_counter_hardware_info_)
    {
        derived_counter_hardware_info_ = new (std::nothrow) GpaDerivedCounterInfo();

        if (nullptr != derived_counter_hardware_info_ && InitializeDerivedCounterHardwareInfo(gpa_counter_accessor))
        {
            derived_counter_info_init_ = true;
        }
    }

    if (derived_counter_info_init_)
    {
        return derived_counter_hardware_info_;
    }

    return nullptr;
}

//------------------------------------------------------------------------------------------------------------------------------------------------------------

void GPA_DerivedCounters::DefineDerivedCounter(const char*         pName,
                                               const char*         pGroup,
                                               const char*         pDescription,
                                               GPA_Data_Type       dataType,
                                               GPA_Usage_Type      usageType,
                                               vector<gpa_uint32>& internalCountersRequired,
                                               const char*         pComputeExpression,
                                               const char*         pUuid)
{
    assert(pName);
    assert(pGroup);
    assert(pDescription);
    assert(dataType < GPA_DATA_TYPE__LAST);
    assert(!internalCountersRequired.empty());
    assert(pComputeExpression);
    assert(strlen(pComputeExpression) > 0);
    assert(pUuid);

    unsigned int index = static_cast<unsigned int>(m_counters.size());

#ifdef ANDROID
    if ((strcmp(pName, "LocalVidMemBytes") == 0) || (strcmp(pName, "PcieBytes") == 0))
    {
        return;
    }
#endif

    m_counters.push_back(GPA_DerivedCounter(index, pName, pGroup, pDescription, dataType, usageType, internalCountersRequired, pComputeExpression, pUuid));
}

void GPA_DerivedCounters::UpdateAsicSpecificDerivedCounter(const char* pName, vector<gpa_uint32>& internalCountersRequired, const char* pComputeExpression)
{
    for (auto& counter : m_counters)
    {
        if (!_strcmpi(pName, counter.m_pName))
        {
            counter.m_internalCountersRequired.clear();
            counter.m_internalCountersRequired = internalCountersRequired;
            counter.m_pComputeExpression       = pComputeExpression;
            return;
        }
    }

    // Errors aside, the counter will not be found if it's not supported on the ASIC
    // e.g.: there's a discrete counter version, but not an SPM version
    {
        std::stringstream o;
        o << "Warning: unable to find counter for ASIC-specific update:" << pName << ". This may be an unsupported SPM counter.";
        GPA_LogMessage(o.str().c_str());
    }
}

void GPA_DerivedCounters::Clear()
{
    m_counters.clear();
    m_countersGenerated = false;
}

gpa_uint32 GPA_DerivedCounters::GetNumCounters() const
{
    return static_cast<gpa_uint32>(m_counters.size());
}

/// Performs a sum of the specified number of stack values
/// T is derived counter type
/// \param[in out] stack RPN counter formula expression stack
/// \param count number of stack items
template <class T>
void SumN(std::vector<T>& stack, int32_t count)
{
    T sum = 0;

    // pop the last count items and add them together
    for (int i = 0; i < count; i++)
    {
        T value = stack.back();
        stack.pop_back();

        sum += value;
    }

    stack.push_back(sum);
}

/// Performs a summation of a two blocks of vectors
/// T is derived counter type
/// \param[in out] stack RPN counter formula expression stack
/// \param vectorWidth width of the vector
template <class T>
void VecSumN(std::vector<T>& stack, int32_t vectorWidth)
{
    // Get values2
    std::vector<T> values2;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        values2.push_back(stack.back());
        stack.pop_back();
    }

    // Get values1
    std::vector<T> values1;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        values1.push_back(stack.back());
        stack.pop_back();
    }

    // Sum and push back
    for (int32_t i = vectorWidth - 1; i >= 0; --i)
    {
        T value = values1[i] + values2[i];
        stack.push_back(value);
    }
}

/// Performs a subtraction of a vector from another vector
/// T is derived counter type
/// \param[in out] stack RPN counter formula expression stack
/// \param vectorWidth width of the vector
template <class T>
void VecSubN(std::vector<T>& stack, int32_t vectorWidth)
{
    // Get values2
    std::vector<T> values2;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        values2.push_back(stack.back());
        stack.pop_back();
    }

    // Get values1
    std::vector<T> values1;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        values1.push_back(stack.back());
        stack.pop_back();
    }

    // Subtract and push back
    for (int32_t i = vectorWidth - 1; i >= 0; --i)
    {
        T value = values1[i] - values2[i];
        stack.push_back(value);
    }
}

/// Performs a divide of a vector by another vector
/// T is derived counter type
/// \param[in out] stack RPN counter formula expression stack
/// \param vectorWidth width of the vector
template <class T>
void VecDivN(std::vector<T>& stack, int32_t vectorWidth)
{
    // Get the divisors
    std::vector<T> divisors;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        divisors.push_back(stack.back());
        stack.pop_back();
    }

    // Get the dividends
    std::vector<T> dividends;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        dividends.push_back(stack.back());
        stack.pop_back();
    }

    // Divide and push back
    for (int32_t i = vectorWidth - 1; i >= 0; --i)
    {
        T value = divisors[i] ? (dividends[i] / divisors[i]) : static_cast<T>(0);
        stack.push_back(value);
    }
}

/// Performs an average of the specified number of stack values
/// T is derived counter type
/// \param[in out] stack RPN counter formula expression stack
/// \param count number of stack items
template <class T>
void AvgN(std::vector<T>& stack, int32_t count)
{
    T value = 0;

    for (int32_t i = 0; i < count; ++i)
    {
        value += stack.back();
        stack.pop_back();
    }

    value /= static_cast<T>(count);

    stack.push_back(value);
}

/// Performs a scalar subtraction of a vector
/// T is derived counter type
/// \param[in out] stack RPN counter formula expression stack
/// \param vectorWidth width of the vector
template <class T>
void ScalarSubN(std::vector<T>& stack, int32_t vectorWidth)
{
    T arg = stack.back();
    stack.pop_back();

    // Get the dividends
    std::vector<T> dividends;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        dividends.push_back(stack.back());
        stack.pop_back();
    }

    // Divide and push back
    for (int32_t i = vectorWidth - 1; i >= 0; --i)
    {
        T value = arg - dividends[i];

        if (value < 0)
        {
            assert(0);
            value = 0;
        }

        stack.push_back(value);
    }
}

/// Performs a scalar divide of a vector
/// T is derived counter type
/// \param[in out] stack RPN counter formula expression stack
/// \param vectorWidth width of the vector
template <class T>
void ScalarDivN(std::vector<T>& stack, int32_t vectorWidth)
{
    T divisor = stack.back();
    stack.pop_back();

    // Get the dividends
    std::vector<T> dividends;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        dividends.push_back(stack.back());
        stack.pop_back();
    }

    // Divide and push back
    for (int32_t i = vectorWidth - 1; i >= 0; --i)
    {
        T value = divisor ? (dividends[i] / divisor) : static_cast<T>(0);
        stack.push_back(value);
    }
}

/// Performs a scalar multiply of a vector
/// T is derived counter type
/// \param[in out] stack RPN counter formula expression stack
/// \param vectorWidth width of the vector
template <class T>
void ScalarMulN(std::vector<T>& stack, int32_t vectorWidth)
{
    // Get the multiplicands
    std::vector<T> multiplicands;

    for (int32_t i = 0; i < vectorWidth; ++i)
    {
        multiplicands.push_back(stack.back());
        stack.pop_back();
    }

    T multiplier = stack.back();
    stack.pop_back();

    assert(multiplier != 0);

    // Multiply and push back
    for (int32_t i = vectorWidth - 1; i >= 0; --i)
    {
        T value = multiplicands[i] * multiplier;
        stack.push_back(value);
    }
}

/// Evaluates a counter formula expression
/// T is derived counter type
/// \param pExpression the counter formula
/// \param[out] pResult the result value
/// \param results list of the hardware counter results
/// \param resultType the counter result type
/// \param pHwInfo the hardware info
/// \return GPA_STATUS_OK on success, otherwise an error code
template <class T, class InternalCounterType>
static GPA_Status EvaluateExpression(const char*                      pExpression,
                                     void*                            pResult,
                                     const vector<const gpa_uint64*>& results,
                                     GPA_Data_Type                    resultType,
                                     const GPA_HWInfo*                pHwInfo)
{
    GPA_Status status = GPA_STATUS_OK;

    if (!pHwInfo)
    {
        assert(nullptr != pHwInfo);
        return GPA_STATUS_ERROR_INVALID_PARAMETER;
    }

    size_t            expressionLen = strlen(pExpression) + 1;
    std::vector<char> pBuf(expressionLen);

    strcpy_s(pBuf.data(), expressionLen, pExpression);

    vector<T> stack;
    T*        pWriteResult = reinterpret_cast<T*>(pResult);

    char* pContext = nullptr;
    pContext;  //TODO: gcc is not considering unused in strtok_s
    char* pch = strtok_s(pBuf.data(), " ,", &pContext);

    while (nullptr != pch)
    {
        if (*pch == '*')
        {
            assert(stack.size() >= 2);

            T p2 = stack.back();
            stack.pop_back();
            T p1 = stack.back();
            stack.pop_back();
            stack.push_back(p1 * p2);
        }
        else if (*pch == '/')
        {
            assert(stack.size() >= 2);

            T p2 = stack.back();
            stack.pop_back();
            T p1 = stack.back();
            stack.pop_back();

            if (p2 != static_cast<T>(0))
            {
                stack.push_back(p1 / p2);
            }
            else
            {
                stack.push_back(static_cast<T>(0));
            }
        }
        else if (*pch == '+')
        {
            assert(stack.size() >= 2);

            T p2 = stack.back();
            stack.pop_back();
            T p1 = stack.back();
            stack.pop_back();
            stack.push_back(p1 + p2);
        }
        else if (*pch == '-')
        {
            assert(stack.size() >= 2);

            T p2 = stack.back();
            stack.pop_back();
            T p1 = stack.back();
            stack.pop_back();
            stack.push_back(p1 - p2);
        }
        else if (*pch == '(')
        {
            // constant
            T   constant   = static_cast<T>(0);
            int scanResult = 0;

            if (resultType == GPA_DATA_TYPE_FLOAT64)
            {
#ifdef _LINUX
                scanResult = sscanf(pch, "(%lf)", reinterpret_cast<gpa_float64*>(&constant));
#else
                scanResult = sscanf_s(pch, "(%lf)", reinterpret_cast<gpa_float64*>(&constant));
#endif  // _LINUX
            }
            else if (resultType == GPA_DATA_TYPE_UINT64)
            {
#ifdef _LINUX
                scanResult = sscanf(pch, "(%llu)", reinterpret_cast<gpa_uint64*>(&constant));
#else
                scanResult = sscanf_s(pch, "(%I64u)", reinterpret_cast<gpa_uint64*>(&constant));
#endif  // _LINUX
            }
            else
            {
                // Unsupported derived counter type
                assert(false);
                return GPA_STATUS_ERROR_INVALID_DATATYPE;
            }

            if (1 != scanResult)
            {
                assert(false);
            }

            stack.push_back(constant);
        }
        else if (_strcmpi(pch, "num_shader_engines") == 0)
        {
            stack.push_back(static_cast<T>(pHwInfo->GetNumberShaderEngines()));
        }
        else if (_strcmpi(pch, "num_shader_arrays") == 0)
        {
            stack.push_back(static_cast<T>(pHwInfo->GetNumberShaderArrays()));
        }
        else if (_strcmpi(pch, "num_simds") == 0)
        {
            stack.push_back(static_cast<T>(pHwInfo->GetNumberSIMDs()));
        }
        else if (_strcmpi(pch, "su_clocks_prim") == 0)
        {
            stack.push_back(static_cast<T>(pHwInfo->GetSUClocksPrim()));
        }
        else if (_strcmpi(pch, "num_prim_pipes") == 0)
        {
            stack.push_back(static_cast<T>(pHwInfo->GetNumberPrimPipes()));
        }
        else if (_strcmpi(pch, "num_cus") == 0)
        {
            stack.push_back(static_cast<T>(pHwInfo->GetNumberCUs()));
        }
        else if (_strcmpi(pch, "TS_FREQ") == 0)
        {
            gpa_uint64 freq = 1u;
            GPA_ASSERT(pHwInfo->GetTimeStampFrequency(freq));
            stack.push_back(static_cast<T>(freq));
        }
        else if (_strcmpi(pch, "max") == 0)
        {
            assert(stack.size() >= 2);

            T p2 = stack.back();
            stack.pop_back();
            T p1 = stack.back();
            stack.pop_back();

            if (p1 > p2)
            {
                stack.push_back(p1);
            }
            else
            {
                stack.push_back(p2);
            }
        }
        else if (_strcmpi(pch, "max4") == 0)
        {
            assert(stack.size() >= 4);

            // initialize the max value to the 1st item
            T maxValue = stack.back();
            stack.pop_back();

            // pop the last 3 items and compute the max
            for (int i = 0; i < 3; i++)
            {
                T value = stack.back();
                stack.pop_back();

                maxValue = (maxValue > value) ? maxValue : value;
            }

            stack.push_back(maxValue);
        }
        else if (_strcmpi(pch, "max10") == 0)
        {
            assert(stack.size() >= 10);

            // initialize the max value to the 1st item
            T maxValue = stack.back();
            stack.pop_back();

            // pop the last 9 items and compute the max
            for (int i = 0; i < 9; i++)
            {
                T value = stack.back();
                stack.pop_back();

                maxValue = (maxValue > value) ? maxValue : value;
            }

            stack.push_back(maxValue);
        }
        else if (_strcmpi(pch, "max16") == 0)
        {
            assert(stack.size() >= 16);

            // initialize the max value to the 1st item
            T maxValue = stack.back();
            stack.pop_back();

            // pop the last 15 items and compute the max
            for (int i = 0; i < 15; i++)
            {
                T value = stack.back();
                stack.pop_back();

                maxValue = (maxValue > value) ? maxValue : value;
            }

            stack.push_back(maxValue);
        }
        else if (_strcmpi(pch, "max24") == 0)
        {
            assert(stack.size() >= 24);

            // initialize the max value to the 1st item
            T maxValue = stack.back();
            stack.pop_back();

            // pop the last 23 items and compute the max
            for (int i = 0; i < 23; i++)
            {
                T value = stack.back();
                stack.pop_back();

                maxValue = (maxValue > value) ? maxValue : value;
            }

            stack.push_back(maxValue);
        }
        else if (_strcmpi(pch, "max32") == 0)
        {
            assert(stack.size() >= 32);

            // initialize the max value to the 1st item
            T maxValue = stack.back();
            stack.pop_back();

            // pop the last 31 items and compute the max
            for (int i = 0; i < 31; i++)
            {
                T value = stack.back();
                stack.pop_back();

                maxValue = (maxValue > value) ? maxValue : value;
            }

            stack.push_back(maxValue);
        }
        else if (_strcmpi(pch, "max44") == 0)
        {
            assert(stack.size() >= 44);

            // initialize the max value to the 1st item
            T maxValue = stack.back();
            stack.pop_back();

            // pop the last 43 items and compute the max
            for (int i = 0; i < 43; i++)
            {
                T value = stack.back();
                stack.pop_back();

                maxValue = (maxValue > value) ? maxValue : value;
            }

            stack.push_back(maxValue);
        }
        else if (_strcmpi(pch, "max64") == 0)
        {
            assert(stack.size() >= 64);

            // initialize the max value to the 1st item
            T maxValue = stack.back();
            stack.pop_back();

            // pop the last 63 items and compute the max
            for (int i = 0; i < 63; i++)
            {
                T value = stack.back();
                stack.pop_back();

                maxValue = (maxValue > value) ? maxValue : value;
            }

            stack.push_back(maxValue);
        }
        else if (_strcmpi(pch, "max80") == 0)
        {
            assert(stack.size() >= 80);

            // initialize the max value to the 1st item
            T maxValue = stack.back();
            stack.pop_back();

            // pop the last 79 items and compute the max
            for (int i = 0; i < 79; i++)
            {
                T value = stack.back();
                stack.pop_back();

                maxValue = (maxValue > value) ? maxValue : value;
            }

            stack.push_back(maxValue);
        }
        else if (_strcmpi(pch, "min") == 0)
        {
            assert(stack.size() >= 2);

            T p2 = stack.back();
            stack.pop_back();
            T p1 = stack.back();
            stack.pop_back();

            if (p1 < p2)
            {
                stack.push_back(p1);
            }
            else
            {
                stack.push_back(p2);
            }
        }
        else if (_strcmpi(pch, "ifnotzero") == 0)
        {
            assert(stack.size() >= 3);

            T condition = stack.back();
            stack.pop_back();
            T resultTrue = stack.back();
            stack.pop_back();
            T resultFalse = stack.back();
            stack.pop_back();

            if (condition != 0)
            {
                stack.push_back(resultTrue);
            }
            else
            {
                stack.push_back(resultFalse);
            }
        }
        else if (_strcmpi(pch, "comparemax4") == 0)
        {
            assert(stack.size() >= 8);

            std::vector<T> values;

            for (int i = 0; i < 4; ++i)
            {
                values.push_back(stack.back());
                stack.pop_back();
            }

            std::vector<T> potentialReturns;

            for (int i = 0; i < 4; ++i)
            {
                // Only consider potential returns where the values[i] is non-zero
                if (values[i])
                {
                    potentialReturns.push_back(stack.back());
                }

                stack.pop_back();
            }

            if (potentialReturns.empty())
            {
                stack.push_back(0);
            }
            else
            {
                auto iter = std::max_element(potentialReturns.begin(), potentialReturns.end());
                stack.push_back(*iter);
            }
        }
        else if (_strcmpi(pch, "sum2") == 0)
        {
            const int valueCount = 2;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum4") == 0)
        {
            const int valueCount = 4;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum8") == 0)
        {
            const int valueCount = 8;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum10") == 0)
        {
            const int valueCount = 10;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum11") == 0)
        {
            const int valueCount = 11;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum12") == 0)
        {
            const int valueCount = 12;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum16") == 0)
        {
            const int valueCount = 16;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum24") == 0)
        {
            const int valueCount = 24;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum32") == 0)
        {
            const int valueCount = 32;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum40") == 0)
        {
            const int valueCount = 40;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum44") == 0)
        {
            const int valueCount = 44;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum64") == 0)
        {
            const int valueCount = 64;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum80") == 0)
        {
            const int valueCount = 80;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum256") == 0)
        {
            const int valueCount = 256;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "sum320") == 0)
        {
            const int valueCount = 320;
            assert(stack.size() >= valueCount);
            SumN(stack, valueCount);
        }
        else if (_strcmpi(pch, "vecsum2") == 0)
        {
            const int32_t vectorWidth = 2;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec2
            VecSumN<T>(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecsum4") == 0)
        {
            const int32_t vectorWidth = 4;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec4
            VecSumN<T>(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecsum8") == 0)
        {
            const int32_t vectorWidth = 8;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec8
            VecSumN<T>(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecsum16") == 0)
        {
            const int32_t vectorWidth = 16;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec16
            VecSumN<T>(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecsum64") == 0)
        {
            const int32_t vectorWidth = 64;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec64
            VecSumN<T>(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecsub2") == 0)
        {
            const int32_t vectorWidth = 2;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec2
            VecSubN<T>(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecsub16") == 0)
        {
            const int32_t vectorWidth = 16;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec16
            VecSubN<T>(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv2") == 0)
        {
            const int32_t vectorWidth = 2;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec2
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv4") == 0)
        {
            const int32_t vectorWidth = 4;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec4
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv8") == 0)
        {
            const int32_t vectorWidth = 8;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec8
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv10") == 0)
        {
            const int32_t vectorWidth = 10;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec10
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv16") == 0)
        {
            const int32_t vectorWidth = 16;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec16
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv20") == 0)
        {
            const int32_t vectorWidth = 20;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec20
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv32") == 0)
        {
            const int32_t vectorWidth = 32;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec32
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv64") == 0)
        {
            const int32_t vectorWidth = 64;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec64
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "vecdiv80") == 0)
        {
            const int32_t vectorWidth = 80;
            assert(stack.size() >= 2 * vectorWidth);  // 2 vectors with vec80
            VecDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "avg2") == 0)
        {
            const int32_t valueCount = 2;
            assert(stack.size() >= valueCount);
            AvgN(stack, valueCount);
        }
        else if (_strcmpi(pch, "avg4") == 0)
        {
            const int32_t valueCount = 4;
            assert(stack.size() >= valueCount);
            AvgN(stack, valueCount);
        }
        else if (_strcmpi(pch, "avg8") == 0)
        {
            const int32_t valueCount = 8;
            assert(stack.size() >= valueCount);
            AvgN(stack, valueCount);
        }
        else if (_strcmpi(pch, "avg16") == 0)
        {
            const int32_t valueCount = 16;
            assert(stack.size() >= valueCount);
            AvgN(stack, valueCount);
        }
        else if (_strcmpi(pch, "avg20") == 0)
        {
            const int32_t valueCount = 20;
            assert(stack.size() >= valueCount);
            AvgN(stack, valueCount);
        }
        else if (_strcmpi(pch, "scalarSub10") == 0)
        {
            const int32_t vectorWidth = 10;
            assert(stack.size() >= (vectorWidth + 1));  // vec10 + 1 scalar
            ScalarSubN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarSub20") == 0)
        {
            const int32_t vectorWidth = 20;
            assert(stack.size() >= (vectorWidth + 1));  // vec20 + 1 scalar
            ScalarSubN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarSub40") == 0)
        {
            const int32_t vectorWidth = 40;
            assert(stack.size() >= (vectorWidth + 1));  // vec40 + 1 scalar
            ScalarSubN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarSub80") == 0)
        {
            const int32_t vectorWidth = 80;
            assert(stack.size() >= (vectorWidth + 1));  // vec80 + 1 scalar
            ScalarSubN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarDiv2") == 0)
        {
            const int32_t vectorWidth = 2;
            assert(stack.size() >= (vectorWidth + 1));  // vec2 + 1 scalar
            ScalarDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarDiv4") == 0)
        {
            const int32_t vectorWidth = 4;
            assert(stack.size() >= (vectorWidth + 1));  // vec4 + 1 scalar
            ScalarDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarDiv8") == 0)
        {
            const int32_t vectorWidth = 8;
            assert(stack.size() >= (vectorWidth + 1));  // vec8 + 1 scalar
            ScalarDivN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarMul2") == 0)
        {
            const int32_t vectorWidth = 2;
            assert(stack.size() >= (vectorWidth + 1));  // vec2 + 1 scalar
            ScalarMulN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarMul4") == 0)
        {
            const int32_t vectorWidth = 4;
            assert(stack.size() >= (vectorWidth + 1));  // vec4 + 1 scalar
            ScalarMulN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarMul8") == 0)
        {
            const int32_t vectorWidth = 8;
            assert(stack.size() >= (vectorWidth + 1));  // vec8 + 1 scalar
            ScalarMulN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarMul16") == 0)
        {
            const int32_t vectorWidth = 16;
            assert(stack.size() >= (vectorWidth + 1));  // vec16 + 1 scalar
            ScalarMulN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarMul20") == 0)
        {
            const int32_t vectorWidth = 20;
            assert(stack.size() >= (vectorWidth + 1));  // vec20 + 1 scalar
            ScalarMulN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarMul32") == 0)
        {
            const int32_t vectorWidth = 32;
            assert(stack.size() >= (vectorWidth + 1));  // vec32 + 1 scalar
            ScalarMulN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarMul40") == 0)
        {
            const int32_t vectorWidth = 40;
            assert(stack.size() >= (vectorWidth + 1));  // vec40 + 1 scalar
            ScalarMulN(stack, vectorWidth);
        }
        else if (_strcmpi(pch, "scalarMul64") == 0)
        {
            const int32_t vectorWidth = 64;
            assert(stack.size() >= (vectorWidth + 1));  // vec64 + 1 scalar
            ScalarMulN(stack, vectorWidth);
        }
        else
        {
            // must be number, reference to internal counter
            gpa_uint32 index;
#ifdef _LINUX
            int scanResult = sscanf(pch, "%d", &index);
#else
            int scanResult = sscanf_s(pch, "%d", &index);
#endif
            UNREFERENCED_PARAMETER(scanResult);
            assert(scanResult == 1);

            if (index < results.size())
            {
                const InternalCounterType internalVal      = *reinterpret_cast<const InternalCounterType*>(results[index]);
                T                         internalValFloat = static_cast<T>(internalVal);
                stack.push_back(internalValFloat);
            }
            else
            {
                // the index was invalid, so the counter result is unknown
                assert(0);
                GPA_LogError("counter registerIndex in equation is out of range.");
                status = GPA_STATUS_ERROR_INVALID_COUNTER_EQUATION;
                break;
            }
        }

        pch = strtok_s(nullptr, " ,", &pContext);
    }

    if (stack.size() != 1)
    {
        std::stringstream ss;
        ss << "Invalid formula: " << pExpression << ".";
        GPA_LogError(ss.str().c_str());
        status = GPA_STATUS_ERROR_INVALID_COUNTER_EQUATION;
    }

    assert(stack.size() == 1);
    *pWriteResult = stack.back();

    return status;
}

GPA_Status GPA_DerivedCounters::ComputeCounterValue(gpa_uint32                       counterIndex,
                                                    const vector<const gpa_uint64*>& results,
                                                    vector<GPA_Data_Type>&           internalCounterTypes,
                                                    void*                            pResult,
                                                    const GPA_HWInfo*                pHwInfo) const
{
    if (nullptr == m_counters[counterIndex].m_pComputeExpression)
    {
        GPA_LogError("Unable to compute counter value: no equation specified.");
        return GPA_STATUS_ERROR_INVALID_COUNTER_EQUATION;
    }

#ifdef AMDT_INTERNAL
    GPA_LogDebugCounterDefs("'%s' equation is %s.", m_counters[counterIndex].m_pName, m_counters[counterIndex].m_pComputeExpression);
#endif

    GPA_Status status = GPA_STATUS_OK;

    if (internalCounterTypes[0] == GPA_DATA_TYPE_UINT64)
    {
        if (m_counters[counterIndex].m_dataType == GPA_DATA_TYPE_FLOAT64)
        {
            status = EvaluateExpression<gpa_float64, gpa_uint64>(
                m_counters[counterIndex].m_pComputeExpression, pResult, results, m_counters[counterIndex].m_dataType, pHwInfo);
        }
        else if (m_counters[counterIndex].m_dataType == GPA_DATA_TYPE_UINT64)
        {
            status = EvaluateExpression<gpa_uint64, gpa_uint64>(
                m_counters[counterIndex].m_pComputeExpression, pResult, results, m_counters[counterIndex].m_dataType, pHwInfo);
        }
        else
        {
            // derived counter type not recognized or not currently supported.
            GPA_LogError("Unable to compute counter value: unrecognized derived counter type.");
            assert(false);
        }
    }
    else
    {
        GPA_LogError("Unable to compute counter value: unrecognized derived counter type.");
        return GPA_STATUS_ERROR_INVALID_DATATYPE;
    }

    return status;
}
